package presenter.duplicate;

import main.InputMode;
import utils.FileBean;
import main.InputBean;
import utils.OnLogListener;
import utils.FileFilter;
import utils.TextUtils;
import utils.ThreadPool;

import java.io.File;
import java.util.*;
import java.util.concurrent.CountDownLatch;

public class FileDuplicator {
    private final Map<String, List<FileBean>> filters = new HashMap<>();
    private final OnLogListener listener;
    private final String targetDir;
    private final String delDir;
    private final long start;
    private final boolean exactMode;//精确删除模式，就是只对主文件内的文件来过滤次文件夹内的文件，次文件夹里面的重复不管

    public static void del(InputBean info, OnLogListener listener) {
        new FileDuplicator(info.path1(), info.path2(), info.mode() == InputMode.DuplicateExact, listener);
    }

    private FileDuplicator(String targetDir, String delDir, boolean exactMode, OnLogListener listener) {
        this.targetDir = targetDir;
        this.delDir = delDir;
        this.exactMode = exactMode;
        this.listener = listener;
        this.start = time();
        check(() -> filter(() -> del(this::end)));
    }

    private void end() {
        long end = time();
        log(String.format("----------任务结束！！！共用时%d秒----------", (int) (end - start)));
        listener.taskFinish();
    }

    private void check(Runnable next) {
        log("任务开始！");
        if (TextUtils.isEmpty(targetDir)) {
            log("地址存在空！");
            listener.taskFinish();
        } else {
            File targetDirs = new File(targetDir);
            if (!targetDirs.isDirectory()) {
                log("地址不是文件夹！");
                listener.taskFinish();
            } else {
                next.run();
            }
        }
    }

    //一级过滤,只根据文件的大小来判断是否一致
    private void filter(Runnable next) {
        work(() -> {
            log("开始过滤！文件分析中，时间可能较长，请稍等...");
            List<FileBean> targets = FileFilter.filter(true, targetDir, null);
            List<FileBean> deletes = TextUtils.isEmpty(delDir) ? new ArrayList<>() : FileFilter.filter(false, delDir, null);
            log(String.format("共分析过滤%d个文件！", (targets.size() + deletes.size())));
            concat(true, targets);//targets和deletes的concat的顺序不能颠倒，因为精确模式下，不过滤次文件夹里面重复文件
            concat(false, deletes);
            next.run();
        });
    }

    //合并filters
    private void concat(boolean target, List<FileBean> items) {
        if (items != null && !items.isEmpty()) {
            for (FileBean item : items) {
                String key = item.key();
                List<FileBean> pairs = filters.get(key);
                if (!target && exactMode && pairs == null) {//只有在精确对比的模式下，才不理会次文件夹里面文件重复的情况
                    continue;
                } else {//普通模式下，主文件夹和次文件夹的重复文件都会被过滤出来
                    if (pairs == null) {
                        pairs = new ArrayList<>();
                        filters.put(key, pairs);
                    }
                    pairs.add(item);
                }
            }
        }
    }

    //开始删除,包括两个比较操作
    private void del(Runnable next) {
        log("----------开始删除操作！----------");
        Set<Map.Entry<String, List<FileBean>>> entries = filters.entrySet();
        if (!entries.isEmpty()) {
            int size = entries.size();
            CountDownLatch latch = new CountDownLatch(size);
            log("latch size = " + size);
            for (Map.Entry<String, List<FileBean>> item : entries) {
                work(() -> {
                    String key = item.getKey();
                    List<FileBean> pairs = item.getValue();


                    if (pairs.size() > 1) {//必须大于1个，不然删除谁？
                        // 过滤大文件，文件size >= 1000M并且name相同，则认为相同文件
                        reFilter(true, pairs, null);
                        reFilter(false, pairs,
                                () -> countDown(key, true, latch));
                    } else {
                        countDown(key, false, latch);
                    }
                });
            }
            try {
                latch.await();
            } catch (InterruptedException e) {
                log(e.getMessage());
            }
        }
        next.run();
    }

    private void countDown(String key, boolean needLog, CountDownLatch latch) {
        if (needLog) {
            log(String.format("countdown after %s 剩余: %d", key, latch.getCount()));
        }
        latch.countDown();
    }

    //因为此集合里面的item大小都一致,需要将key重新处理
    private void reFilter(boolean bigFileMode, List<FileBean> items, Runnable next) {
        StringBuilder builder = new StringBuilder();
        builder.append("------------------------------------------------------------\n");

        try {
            Map<String, List<FileBean>> result = new HashMap<>();
            builder.append(String.format("大小为 %s 的 %d 个文件集合需要以文件 %s 重新进行过滤,请稍等......\n", items.get(0).key(), items.size(), (bigFileMode ? "key = size + name" : "key = md5")));

            for (FileBean item : items) {
                String key = "";  //重新设置key
                if (bigFileMode) {
                    if (item.bigFile()) {//只过滤大文件
                        key = item.bigKey();
                    }
                } else {
                    key = item.reKey();
                }

                if (!TextUtils.isEmpty(key)) {
                    List<FileBean> pairs = result.get(key);
                    if (pairs == null) {
                        pairs = new ArrayList<>();
                        result.put(key, pairs);
                    }
                    pairs.add(item);
                }
            }

            //以新key进行的删除操作
            Set<Map.Entry<String, List<FileBean>>> entries = result.entrySet();
            if (!entries.isEmpty()) {
                for (Map.Entry<String, List<FileBean>> item : entries) {
                    List<FileBean> pairs = item.getValue();
                    if (pairs != null && !pairs.isEmpty() && pairs.size() > 1) {//必须大于1个，不然删除谁？
                        del(pairs, new OnLogListener() {
                            @Override
                            public void log(String content) {
                                builder.append(content);
                                builder.append("\n");
                            }

                            @Override
                            public void taskFinish() {

                            }
                        });
                    }
                }
            }
        } catch (Exception ex) {
            builder.append(String.format("error -> %s\n", ex.getMessage()));
        } finally {
            listener.log(builder.toString());
            if (next != null) {
                next.run();
            }
        }
    }

    private void del(List<FileBean> pairs, OnLogListener listener) {
        List<FileBean> targets = new ArrayList<>();
        List<FileBean> deletes = new ArrayList<>();
        for (FileBean item : pairs) {
            if (item.target()) {
                targets.add(item);
            } else {
                deletes.add(item);
            }
        }

        if (targets.isEmpty()) {//没有目标,只有删除
            reversedOne(deletes, listener);
        } else if (deletes.isEmpty()) {//有目标，没有删除
            reversedOne(targets, listener);
        } else {//两个目录都有
            reversedOne(targets, listener);
            for (FileBean item : deletes) {
                item.del(listener);
            }
        }

        //释放集合里面的数据
        pairs.clear();
    }

    //只保留list中的一个
    private void reversedOne(List<FileBean> items, OnLogListener listener) {
        boolean firstItem = true;

        Collections.sort(items);//排序,文件名短的放前面,防止保留了xxxx(1).jpg

        for (FileBean item : items) {//只保留目标文件夹的第一个Item
            if (firstItem) {
                //保留第一个Item
                listener.log("保留的文件：" + item.path());
            } else {
                item.del(listener);
            }
            firstItem = false;
        }
    }

    private void work(Runnable action) {
        ThreadPool.work(action);
    }

    private long time() {
        return System.currentTimeMillis() / 1000;
    }

    private void log(String content) {
        listener.log(content);
    }
}

